use crate::address::{Wallet, Wallets};
use crate::blockchain::Blockchain;
use crate::util::{base58decode, base58encode, bytes_to_hex_string, clear_key_raw_str, hash_pub_key, hex_string_to_bytes, push_key_label};
use p256::ecdsa::signature::{Signer, Verifier};
use p256::ecdsa::{Signature, SigningKey, VerifyingKey};
use p256::{NistP256, PublicKey, SecretKey};
use rand::Rng;
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::collections::HashMap;
use p256::pkcs8::der::Encode;
use crate::utxo_set::UTXOSet;

#[derive(Serialize, Deserialize)]
#[derive(Clone)]
pub struct TXInput {
    pub txid: Vec<u8>,
    pub vout: usize,
    pub signature: Vec<u8>,
    pub pub_key: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
#[derive(Clone)]
pub struct TXOutput {
    pub value: i32,
    pub pub_key_hash: Vec<u8>,
}

#[derive(Serialize, Deserialize)]
#[derive(Clone)]
pub struct Transaction {
    pub id: Vec<u8>,
    pub vin: Vec<TXInput>,
    pub vout: Vec<TXOutput>,
}

impl Transaction {
    pub fn new_coinbase_tx(to: &Vec<u8>, data: &str) -> Transaction {
        let mut data_t = data;
        let default_data = format!("Reward to '{:?}'", to);
        let pub_key_hash = to[1..to.len() - 4].to_vec();
        if data_t == "" {
            data_t = default_data.as_str();
        }

        let txout = TXOutput {
            value: 10,
            pub_key_hash,
        };

        Transaction {
            id: vec![],
            vin: vec![],
            vout: vec![txout],
        }
    }

    pub fn is_coninbase(&self) -> bool {
        self.vin.len() == 0
    }

    pub fn new_utxo_transaction(from: &Vec<u8>, to: &Vec<u8>, amount: i32, bc: &Blockchain, us: &UTXOSet) -> Transaction {
        let mut inputs = vec![];
        let mut outputs = vec![];
        let wallets = Wallets::new_wallets();
        let wallet = wallets.get_wallet(&base58encode(from));

        let (acc, valid_outputs) = us.find_spendable_outputs(&from[1..from.len() - 4].to_vec(), amount);

        if acc < amount {
            panic!("ERROR: Not enough funds");
        }

        for txid in valid_outputs.keys() {
            let outs = valid_outputs.get(txid).unwrap();
            for out in outs {
                let input = TXInput {
                    txid: txid.clone(),
                    vout: out.vout_idx,
                    signature: vec![],
                    pub_key: wallet.public_key.clone(),
                };
                inputs.push(input);
            }
        }

        outputs.push(TXOutput {
            value: amount,
            pub_key_hash: to[1..to.len()-4].to_vec(),
        });
        if acc > amount {
            outputs.push(TXOutput {
                value: acc - amount,
                pub_key_hash: from[1..from.len()-4].to_vec(),
            })
        }

        let mut tx = Transaction {
            id: vec![],
            vin: inputs,
            vout: outputs,
        };
        tx.id = tx.hash();
        bc.sign_transaction(&mut tx, &wallet.private_key);

        tx
    }

    pub fn sign(&mut self, priv_key: &SecretKey, prev_txs: &HashMap<Vec<u8>, Transaction>) {
        if self.is_coninbase() {
            return;
        }

        let mut tx_copy = self.trimmed_copy();

        for in_id in 0..tx_copy.vin.len() {
            let prev_tx = prev_txs.get(&tx_copy.vin[in_id].txid).unwrap();
            tx_copy.vin[in_id].pub_key = prev_tx.vout[tx_copy.vin[in_id].vout as usize].pub_key_hash.clone();
            tx_copy.id = (&mut tx_copy).hash();
            tx_copy.vin[in_id].pub_key = vec![];

            let signing_key: SigningKey = priv_key.into();
            let signature = signing_key.sign(tx_copy.id.as_slice());
            self.vin[in_id].signature = signature.to_string().into_bytes();
            // println!("sign signature_s={}, tx_copy.id={:?}", signature.to_string(), tx_copy.id);
        }
    }

    pub fn verify(&self, prev_txs: &HashMap<Vec<u8>, Transaction>) -> bool {
        let mut tx_copy = self.trimmed_copy();

        for in_id in 0..self.vin.len() {
            let vin = &self.vin[in_id];

            let prev_tx = prev_txs.get(&vin.txid).unwrap();
            tx_copy.vin[in_id].pub_key = prev_tx.vout[vin.vout as usize].pub_key_hash.clone();
            tx_copy.id = tx_copy.hash();
            tx_copy.vin[in_id].pub_key = vec![];

            let pub_key_s = push_key_label(&String::from_utf8(vin.pub_key.clone()).unwrap(), "PUBLIC");
            // println!("pub_key_s: {}", pub_key_s);
            let public_key = pub_key_s.parse::<PublicKey>().unwrap();
            let verifying_key: VerifyingKey = public_key.into();

            let signature_s = String::from_utf8(vin.signature.clone()).unwrap();
            // println!("verify signature_s={}, tx_copy.id={:?}", signature_s, tx_copy.id);
            let signature:Signature = signature_s.parse::<Signature>().unwrap();

            if !verifying_key.verify(&tx_copy.id, &signature).is_ok() {
                return false;
            }
        }

        true
    }

    pub fn trimmed_copy(&self) -> Transaction {
        let mut inputs = vec![];
        let mut outputs = vec![];

        for vin in &self.vin {
            inputs.push(TXInput {
                txid: vin.txid.clone(),
                vout: vin.vout,
                signature: vec![],
                pub_key: vec![],
            })
        }

        for vout in &self.vout {
            outputs.push(TXOutput {
                value: vout.value,
                pub_key_hash: vout.pub_key_hash.clone(),
            })
        }

        Transaction { id: self.id.clone(), vin: inputs, vout: outputs }
    }

    pub fn hash(&self) -> Vec<u8> {
        let mut tx_copy = self.clone();
        tx_copy.id = vec![];

        let mut hasher = Sha256::new();
        hasher.update(serde_json::to_string(&tx_copy).unwrap());
        hasher.finalize().to_vec()
    }
}

impl TXInput {
    pub fn uses_key(&self, pub_key_hash: &Vec<u8>) -> bool {
        let locking_hash = hash_pub_key(&self.pub_key);
        locking_hash == *pub_key_hash
    }
}

impl TXOutput {
    pub fn lock(&mut self, address: &Vec<u8>) {
        let mut pub_key_hash = base58decode(&bytes_to_hex_string(address));
        let pub_key_hash = pub_key_hash[1..pub_key_hash.len() - 4].to_vec();
        self.pub_key_hash = pub_key_hash;
    }

    pub fn is_locked_with_key(&self, pub_key_hash: &Vec<u8>) -> bool {
        self.pub_key_hash == *pub_key_hash
    }
}


#[test]
pub fn test_relation_of_tovec_str() {
    let s = "Helloworld";
    let v = s.as_bytes().to_vec();
    println!("s as_slice={:?}", s.as_bytes().to_vec().as_slice());
    println!("s==v:{}", s == &String::from_utf8(v).unwrap());
}
